Skip to content

feat: add delete repository functionality#42

Open
Selene29 wants to merge 6 commits intorepowise-dev:mainfrom
Selene29:feat/delete-repo
Open

feat: add delete repository functionality#42
Selene29 wants to merge 6 commits intorepowise-dev:mainfrom
Selene29:feat/delete-repo

Conversation

@Selene29
Copy link
Copy Markdown
Contributor

@Selene29 Selene29 commented Apr 5, 2026

Summary

  • Add delete_repository and list_page_ids CRUD helpers, plus delete_many for FTS cleanup
  • Add DELETE /api/repos/{repo_id} endpoint that cascades through DB rows and FTS index
  • Add repowise delete CLI command with interactive repo selection and --force flag
  • Add delete button to the dashboard UI (hover icon) with confirmation dialog
  • Add delete button to repo settings page under a Danger Zone card — fixes single-repo setups where the dashboard auto-redirects past the list

Test plan

  • Unit tests for CRUD delete_repository and list_page_ids
  • API endpoint tests for DELETE /api/repos/{repo_id} (success + 404)
  • Manual: run repowise delete CLI and verify repo is removed
  • Manual: click delete button in dashboard, confirm dialog, verify repo disappears
  • Manual: navigate to /repos/{id}/settings, verify Danger Zone section with Delete Repository button

@RaghavChamadiya
Copy link
Copy Markdown
Collaborator

Full-stack delete is well done. CRUD, API, CLI, and both UI surfaces (dashboard and Danger Zone) are all covered. The FTS cleanup before CASCADE delete is the right order.

Two issues I found in the code:

  1. CLI argument parsing bug (delete_cmd.py): There are two positional optional arguments, repo_id and path. Click assigns positional args left-to-right, so repowise delete /path/to/repo would assign the path string to repo_id, then fail when trying to look it up as a UUID. The path argument is effectively unreachable. Suggest making path an option (--path) instead of a positional argument.

  2. FTS skip when None (repos.py): In delete_repo, FTS cleanup is skipped when fts is None. If FTS was not initialized on a cold start, old FTS rows would linger. Is this a real scenario? If FTS is always initialized before any delete could happen, a comment explaining that would help future readers.

Logic and tests otherwise look solid. Fix the CLI argument issue and this is ready to merge.

@RaghavChamadiya
Copy link
Copy Markdown
Collaborator

Took another look at this. The FTS comment addresses my concern, and re-reading the CLI args I see path is already a --option not positional, so that's fine.

Just needs a rebase on main since there are conflicts now. Once that's resolved I'll merge.

@Selene29
Copy link
Copy Markdown
Contributor Author

rebased

Copy link
Copy Markdown
Collaborator

@swati510 swati510 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really clean PR, the ordering (collect page_ids, FTS cleanup, CASCADE delete) is exactly right and the tests cover both paths. Two things worth addressing:

  1. UX bug: after deleting from /repos/[id]/settings, the web UI calls router.refresh() which re-renders the current route. That route will then 404 because the repo is gone. On the settings page the button should router.push("/") after success. From the dashboard page router.refresh() is fine.

  2. FTS delete_many iterates one DELETE per page_id. For a repo with thousands of pages that's thousands of sqlite round trips. A single DELETE FROM page_fts WHERE page_id IN (...) inside the existing transaction would be much faster. Not a blocker but easy win.

Otherwise LGTM, happy to approve once the settings-page redirect is fixed.

Comment thread packages/web/src/components/repos/delete-repo-button.tsx Outdated
Comment thread packages/core/src/repowise/core/persistence/search.py Outdated
Persistence layer for repository deletion. CASCADE on FK constraints
handles child tables; FTS5 needs explicit cleanup via delete_many.
Cleans up FTS index before CASCADE-deleting the repository and all
child rows (pages, graph, symbols, git metadata, decisions, etc.).
Lists repos in a numbered table, lets user pick one, confirms,
then cleans FTS and CASCADE-deletes the repo. Supports --force.
Trash icon appears on hover in the repo list. Clicking shows a
confirmation dialog, then calls DELETE /api/repos/{id} and refreshes.
Reviewer feedback:
- path as a second positional arg was unreachable (Click assigns
  left-to-right, so a path string would land in repo_id). Now --path/-p.
- Added comment explaining fts is always initialized in lifespan, so
  the None guard is purely defensive.
- Settings page now redirects to "/" after delete instead of refreshing
  the now-404 route. Adds optional redirectTo prop on DeleteRepoButton.
- FTS delete_many uses a single DELETE ... WHERE page_id IN (...) per
  chunk of 500 ids instead of one round-trip per page.
@Selene29
Copy link
Copy Markdown
Contributor Author

Rebased on main and addressed both points from the latest review:

  1. Settings page redirect (swati510 Bump next-mdx-remote from 5.0.0 to 6.0.0 #1): Added an optional redirectTo prop on DeleteRepoButton. The dashboard still uses router.refresh(); the settings page now passes redirectTo="/" so it router.push("/") after the repo is gone instead of re-rendering a 404 route.
  2. FTS delete_many perf (swati510 Bump next from 15.5.13 to 15.5.14 #2): Replaced the per-id loop with a single DELETE FROM page_fts WHERE page_id IN (...) per 500-id chunk inside the existing transaction. Chunked to stay under SQLite's 999-parameter limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants